## Sid Meier's Civilization 4
## Copyright Firaxis Games 2005
##
## JZoC by Jeckel.
##

from CvPythonExtensions import *

import Popup as PyPopup
import CvUtil
import sys

import PyHelpers
import CvConfigParser

import JZoCUtils
import JZoCFortUtils

#######SD Tool Kit#######

import SdToolKit
sdEcho         = SdToolKit.sdEcho
sdModInit      = SdToolKit.sdModInit
sdModLoad      = SdToolKit.sdModLoad
sdModSave      = SdToolKit.sdModSave
sdEntityInit   = SdToolKit.sdEntityInit
sdEntityExists = SdToolKit.sdEntityExists
sdGetVal       = SdToolKit.sdGetVal
sdSetVal       = SdToolKit.sdSetVal
sdEntityWipe   = SdToolKit.sdEntityWipe

############################

# globals
gc = CyGlobalContext()


############################################################################

# Should units movement be treated as 1 when moving inside an
# enemy Zone of Control.
# True - Units only move 1 square at a time in enemy ZoC.
# False - Units get no movement penalty inside enemy ZoC.
# Default = True
g_bZoCSlowsEnemyMovement = True


# ######## These options allow control over the specific abilities of Forts. ########


# Should the Forts get an attack on enemy units when they pass the fort.
# True - Forts can attack passing enemy units.
# False - Forts will not attack passing enemy units.
# Default = True
g_bFortsCanAttackEnemy = True


# How many units must man a Zone of Control for its abilities to take effect.
# If you do not have atleast this number of units in a fort it will not slow or
# attack enemy units.
# This number must be an integar equal to 0 or higher.
# If value is less then 0 then it will default to 1.
# If value is 0 then all forts will fire on all units.
# Default = 1
g_iFortRequiredStaffUnits = 1


# What is the base percent chance that Forts will hit any given unit.
# Valid values are 1 to 100.
# All other values will default to 35.
# Default = 35
g_iFortBaseAttackChance = 75


# What is the max percent damage that a single Fort attack can do to a unit.
# Valid values are 1 to 100.
# All other values will default to 20.
# Default = 20
g_iFortBaseMaxAttackDamage = 20


# Should multiple Forts covering the same tile give a bonus to the max damage
# those forts can deal to units in that tile in a single attack.
# Example, if 3 Forts are all adjacent to the same desert tile, then they would have
# a chance to do slightly more damage to units in that one desert tile then they would
# in tiles that only 1 or 2 forts were adjacent to.
# True - Multiple Fort coverage gives max damage bonus.
# False -  No bonus for multiple Forts around a tile.
# Default = True
g_bFortNetworkGivesMaxDamageBonus = True


# If 'Fort Network Gives Max Damage Bonus = True'
# For each Fort after the first, this bonus will be added to max damage for each fort attack.
# This cumulative bonus is added to all forts sharing a given tile.
# Example, Rome has the following Zone of Control
# 
# hill1  hill2  hill3
# fort1  fort2  fort3
# 
# snow1  snow2  hill4
# unitA  unitB  fort4
# 
# hill5  hill6  hill7
# fort5  fort6  fort7
# 
# and going with the default values of
# Fort Base Max Attack Damage = 20
# Fort Network Max Damage Modifier = 5
# This gives fort1, fort2, fort5, and fort6 a max damage of 40 percent per hit on unitA.
# All seven forts would then have a max damage of 55 percent on unitB.
# 
# The valid values are -100 to 100.
# Default = 5
g_iFortNetworkMaxDamageModifier = 5


# What is the max number of times a single Fort can attack any given player turn.
# Note: In a game with 7 players (6 players + the barbarians) each fort will
# 	be able to attack this many times on each player's turn, for a total
# 	of 7 times this value in attacks each game turn.
# 0 will give Forts infinate attacks.
# Default = 20
g_iFortMaxAttacksPerPlayerTurn = 20


# ######## These are misc options. ########


# Do you want to see messages when units are slowed or attack by forts.
# True - Show all ZoC messages.
# False - Don't show ZoC messages.
# Default = True
g_bShowZoCMessages = True

############################################################################

#Fort Required Staff Units = 1
#Fort Base Attack Chance = 35
#Fort Base Max Attack Damage = 20
#Fort Network Gives Max Damage Bonus = True
#Fort Network Max Damage Modifier = 5
#Fort Max Attacks Per Player Turn = 20


#g_iFortRequiredStaffUnits
#g_iFortBaseAttackChance
#g_iFortBaseMaxAttackDamage
#g_bFortNetworkGivesMaxDamageBonus
#g_iFortNetworkMaxDamageModifier
#g_iFortMaxAttacksPerPlayerTurn




def loadConfigurationData():
	global g_bZoCSlowsEnemyMovement
	global g_bFortsCanAttackEnemy
	global g_iFortRequiredStaffUnits
	global g_iFortBaseAttackChance
	global g_iFortBaseMaxAttackDamage
	global g_bFortNetworkGivesMaxDamageBonus
	global g_iFortNetworkMaxDamageModifier
	global g_iFortMaxAttacksPerPlayerTurn
	global g_bShowZoCMessages

	# Load the ViSa Ini file containing all of the configuration information
	config = CvConfigParser.CvConfigParser("ViSa_v3.ini")

	# If the ViSa_v3.ini file was found then load its values
	if(config != None):
		g_bZoCSlowsEnemyMovement = config.getboolean("JZoC Mod", "ZoC Slows Enemy Movement", True)
		g_bFortsCanAttackEnemy = config.getboolean("JZoC Mod", "Forts Can Attack Enemy", True)
		g_bFortNetworkGivesMaxDamageBonus = config.getboolean("JZoC Mod", "Fort Network Gives Max Damage Bonus", True)
		g_bShowZoCMessages = config.getboolean("JZoC Mod", "Show ZoC Messages", True)

		g_iFortRequiredStaffUnits = config.getint("JZoC Mod", "Fort Required Staff Units", 1)
		if (g_iFortRequiredStaffUnits < 0):
			g_iFortRequiredStaffUnits = 1

		g_iFortBaseAttackChance = config.getint("JZoC Mod", "Fort Base Attack Chance", 75)
		if (g_iFortBaseAttackChance < 1) or (g_iFortBaseAttackChance > 100):
			g_iFortBaseAttackChance = 75

		g_iFortBaseMaxAttackDamage = config.getint("JZoC Mod", "Fort Base Max Attack Damage", 20)
		if (g_iFortBaseMaxAttackDamage < 1) or (g_iFortBaseMaxAttackDamage > 100):
			g_iFortBaseMaxAttackDamage = 20

		g_iFortNetworkMaxDamageModifier = config.getint("JZoC Mod", "Fort Network Max Damage Modifier", 5)
		if (g_iFortNetworkMaxDamageModifier < -100) or (g_iFortNetworkMaxDamageModifier > 100):
			g_iFortNetworkMaxDamageModifier = 5

		g_iFortMaxAttacksPerPlayerTurn = config.getint("JZoC Mod", "Fort Max Attacks Per Player Turn", 20)
		if (g_iFortMaxAttacksPerPlayerTurn < 0):
			g_iFortMaxAttacksPerPlayerTurn = 20


class JZoC:

	###################################################################
	# These methods produce messages in the log and/or on the screen. #
	###################################################################
	
	def logCombatMessage(self, ePlayer, szMessage):
		strMessage = str(szMessage)
		CyInterface().addCombatMessage(ePlayer, strMessage)

	def alertActivePlayer(self, message, iColor = -1):

		if (not g_bShowZoCMessages):
			return None

		ePlayer = gc.getGame().getActivePlayer()
		eventMessageTimeLong = gc.getDefineINT("EVENT_MESSAGE_TIME_LONG")
		szIcon = None
		iFlashX = 0
		iFlashY = 0

		szString = str(message)

		CyInterface().addMessage(ePlayer, True, eventMessageTimeLong,
					szString, None, 0, szIcon, ColorTypes(iColor),
					iFlashX, iFlashY, True, True)

	def alertIconPlayerFortAttacks(self, message, pUnit, iColor = 7):

		if (not g_bShowZoCMessages):
			return None

		ePlayer = gc.getGame().getActivePlayer()
		eventMessageTimeLong = gc.getDefineINT("EVENT_MESSAGE_TIME_LONG")
		szIcon = "Art/Interface/Buttons/Actions/fortify.dds"
		#iFlashX = pUnit.getX()
		#iFlashY = pUnit.getY()

		strMessage = str(message)

		CyInterface().addMessage(ePlayer, True, eventMessageTimeLong,
					strMessage, None, 0, szIcon, ColorTypes(iColor),
					pUnit.getX(), pUnit.getY(), True, True)

	##########################################################
	# These methods perform work and do not return anything. #
	##########################################################

	def runFortAttack(self, pUnit, pEnemyZoCPlot, iRandAttack, iRandDamage):

		# Return immediately if the unit passed in is invalid
		if (pUnit == None or pUnit.isNone() or pEnemyZoCPlot == None or pEnemyZoCPlot.isNone() or iRandAttack > g_iFortBaseAttackChance or iRandDamage < 1):
			return None
			
		# Return immediately if the unit passed in is invalid
#		if (pUnit.isNone()):
#			return None

		# Return immediately if the plot passed in is invalid
#		if (pEnemyZoCPlot == None):
#			return None
			
		# Return immediately if the plot passed in is invalid
#		if (pEnemyZoCPlot.isNone()):
#			return None

		# Does the Fort randomly attack
#		if (iRandAttack > g_iFortBaseAttackChance):
#                        return None

		# Was random damage greater then 0
#                if (iRandDamage < 1):
#                        return None

		loadConfigurationData()

		#sdEcho("start fort attack")

		iUnitType = pUnit.getUnitType()

		strExemptTypesCivil = ["UNIT_WORKER", "UNIT_SETTLER"]
		strExemptTypesReligion = []
		strExemptTypesGreatPeople = []
		strExemptTypesOther = []

		strExemptTypesAll = strExemptTypesCivil + strExemptTypesReligion + strExemptTypesGreatPeople + strExemptTypesOther

		iExemptTypes = []

		for strExemptType in strExemptTypesAll:
			iExemptType = gc.getInfoTypeForString(strExemptType)
			iExemptTypes += [iExemptType]

		if (iUnitType in iExemptTypes):
			return None

		# g_iFortBaseMaxAttackDamage
		# g_bFortNetworkGivesMaxDamageBonus
		# g_iFortNetworkMaxDamageModifier

		iUnitCurrentHP = pUnit.currHitPoints()

		point = pUnit.plot().getPoint()

		#sdEcho("Unit attacked")

		# effectType = gc.getInfoTypeForString('EFFECT_ARTILLERY_SHELL_EXPLODE')
		# To DO:
		# Make the sounds and effect Era specific, arrows for ancient, catapult for classical
		# CyEngine().triggerEffect(effectType, point)
		CyAudioGame().Play3DSound("AS3D_UN_GRENADE_EXPLODE",point.x,point.y,point.z)

		# Was the attack fatal
		if (iRandDamage >= iUnitCurrentHP):
			#sdEcho("unit killed")
			#targetUnitEntity = CyUnitEntity(pUnit)
			#iWho = targetUnitEntity.GetUnitsCurrentlyAlive()-1
			#targetUnitEntity.GetSubEntity(iWho).PlayAnimationPath(AnimationPathTypes.ANIMATIONPATH_MELEE_DIE)
			pUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_DIE"))
			pUnit.kill(True, iPlayer)

			iPlayer = pUnit.getOwner()
			pKiller = gc.getPlayer(iPlayer)
			strKillerName = pKiller.getName()
			strKillerCivAdj = pKiller.getCivilizationAdjective(0)
			strWorstEnemyName = pKiller.getWorstEnemyName()

			message = "%s Zone of Control kills %s." %(pUnit.getNameNoDesc(), strKillerCivAdj)
			self.alertIconPlayerFortAttacks(message, pUnit)

		else:
			#sdEcho("unit hit")
			iNewDamage = pUnit.getDamage() + iRandDamage
			pUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_DAMAGE"))
			pUnit.setDamage(iNewDamage, True)
			message1 = "%s hit for " %(pUnit.getName()) 
			message2 = "%d damage." %(iRandDamage)
			messageTotal = message1 + message2
			self.alertIconPlayerFortAttacks(messageTotal, pUnit)

	def runZoCActions(self, pUnit, pEnemyZoCPlot, iRandAttack, iRandDamage):

		# Return immediately if the unit passed in is invalid
		if (pUnit == None or pUnit.isNone() or pEnemyZoCPlot == None or pEnemyZoCPlot.isNone()):
			return None
			
		# Return immediately if the unit passed in is invalid
#               if (pUnit.isNone()):
#			return None

		# Return immediately if the plot passed in is invalid
#		if (pEnemyZoCPlot == None):
#			return None
			
		# Return immediately if the plot passed in is invalid
#		if (pEnemyZoCPlot.isNone()):
#			return None

		loadConfigurationData()

		# See if the plot has a Fort Improvement ontop of it
		if (pEnemyZoCPlot.getImprovementType() == gc.getInfoTypeForString("IMPROVEMENT_FORT")):

			# If there is a fort then run all the fort code
			#sdEcho("is fort")

			# Check if the unit should have movement effected
			if (g_bZoCSlowsEnemyMovement):
				JZoCUtils.setUnitMovementFinished(pUnit)

			# Check if the unit should be attacked by the fort
			if (g_bFortsCanAttackEnemy):
				self.runFortAttack(pUnit, pEnemyZoCPlot, iRandAttack, iRandDamage)

			
			

		# Since it wasn't a fort, is it the second kind of ZoC HQ
		# Note: There will be an elif for each ZoC HQ
		#       In order to avoid using the Warlords only improvement destroyed event handler
		#       the sdToolKit data for ZoCs is wiped in the else statement if none of the
		#       elif statments is true.
		#       This is ment to be temperary until I make the ZoC HQs dynamicaly called
		#       from a list.
		elif (1 == 2):
			pass

		else:
			# This ZoC is invalid and needs to be removed
			JZoCUtils.delZoCPlotData(pEnemyZoCPlot)


	################################################
	# These last methods are the onEvent handlers. #
	################################################


	def doImprovementBuilt(self, argsList):
		'Improvement Built'
		iImprovement, iX, iY = argsList

		ImprovementFort = gc.getInfoTypeForString("IMPROVEMENT_FORT")

		pPlot = CyMap().plot(iX, iY)

		if (iImprovement == ImprovementFort):
			JZoCUtils.addZoCPlotData(pPlot)



	def doUnitMove(self, argsList):
		'unit move'
		pPlot, pUnit = argsList

		iRandAttack = CyGame().getSorenRandNum(101, "Random Damage Seed")

		iX = pUnit.getX()
		iY = pUnit.getY()

		pUnitPlot = pUnit.plot()

		iPlayer = pUnit.getOwner()

		loadConfigurationData()

		# g_iFortBaseMaxAttackDamage
		# g_bFortNetworkGivesMaxDamageBonus
		# g_iFortNetworkMaxDamageModifier
		iMaxDamage = g_iFortBaseMaxAttackDamage
		iEnemyForts = JZoCFortUtils.getEnemyFortZoCCount(pUnitPlot, iPlayer)
		if (not g_bFortNetworkGivesMaxDamageBonus) or (iEnemyForts < 2):
			iMaxDamage = g_iFortBaseMaxAttackDamage
		else:
			iMaxDamageMod = iEnemyForts * g_iFortNetworkMaxDamageModifier
			iMaxDamage = g_iFortBaseMaxAttackDamage + iMaxDamageMod
			if (iMaxDamage > 101):
				iMaxDamage = 101

		iRandDamage = CyGame().getSorenRandNum(iMaxDamage, "Random Damage Seed")

		# Did the unit just enter a friendly ZoC center tile
		if (JZoCUtils.isPlotZoC(pUnitPlot)):
			pass
		else:
			if (JZoCUtils.isPlotInZoC(pUnitPlot)):
				# pUnit is in a ZoC, so lets check if it is an enemy ZoC and
				# see if an enemy HQ is in any/all/some of the 8 tiles around pUnit
				if (JZoCUtils.isPlotInEnemyZoC(pUnitPlot, iPlayer)):
					# Your in an enemy ZoC so cycle through the 8 surrounding tiles to
					# find any and all enemy HQs
					# Cycle through the x values..
					for iiX in range(iX-1, iX+2, 1):
						# and through each x values's y value...
						for iiY in range(iY-1, iY+2, 1):

							# Set the current plot obj
							pTempPlot = CyMap().plot(iiX,iiY)

							# Is pTempPlot is a ZoC Head Quarters
							if (JZoCUtils.isPlotEnemyZoC(pTempPlot, iPlayer)):
								# umm, this is the meat, here is where I run the methods to do things to units
								self.runZoCActions(pUnit, pTempPlot, iRandAttack, iRandDamage)
								continue
							else:
								# No Zone of Control Head Quarters so continue through the iter
								# ToDo: add a check to see if pTempPlot had neutral/friendly/vacant ZoC HQs
								continue

				else:
					# You aren't in enemy ZoC
					# ToDO: Maybe add somthing for friendly ZoC
					#       Quick Idea, a promotion that adds +1 movement on roads, or -1 terrain movement cost
					pass

			else:
				# pUnit isn't in a ZoC so this code doesn't need to do anything
				pass







				
				    

		#strFortID = str(pPlot.getX()) + "-" + str(pPlot.getY())

		#if (pPlot.getImprovementType() == gc.getInfoTypeForString("IMPROVEMENT_FORT")):
			#sdEcho("Is Fort")

			#if (sdEntityExists("JFortMod", strFortID)):
			#        message = "Fort built in year %s" %(sdGetVal("JFortMod", strFortID, "YEAR_BUILT"))
			#        sdEcho(message)
			#else:
			#        self.setupFortData(pPlot)
			#        sdEcho("Fort data added")
		#else:
			#sdEcho("No Fort")


	
			









	#################################################################
	# These methods are just here for refence and aren't ever used. #
	#################################################################

	def runDeclareWar(self, p1Unit, p2Unit):

		#pPlot.getCulture(PlayerType eIndex)

		i1Player = p1Unit.getOwner()
		p1Player = gc.getPlayer(i1Player)
		e1Team = p1Player.getTeam()

		p2Player = gc.getPlayer(p2Unit.getOwner())
		e2Team = gc.getTeam(p2Player.getTeam())
		e2Team.declareWar(e1Team, False)


	def killUnit(self, pUnit, iPlayer):
		#targetUnitEntity = CyUnitEntity(pUnit)
		#iWho = targetUnitEntity.GetUnitsCurrentlyAlive()-1
		#targetUnitEntity.GetSubEntity(iWho).PlayAnimationPath(AnimationPathTypes.ANIMATIONPATH_MELEE_DIE)
		pUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_DIE"))
		pUnit.kill(True, iPlayer)

		pKiller = gc.getPlayer(iPlayer)
		strKillerName = pKiller.getName()
		strKillerCivAdj = pKiller.getCivilizationAdjective(0)
		strWorstEnemyName = pKiller.getWorstEnemyName()

		message = "%s Zone of Control kills %s." %(pUnit.getNameNoDesc(), strKillerCivAdj)
		self.alertIconPlayerFortAttacks(message, pUnit)

	def damageUnit(self, pUnit, iDamage):
		unitNewDamage = pUnit.getDamage() + iDamage
		pUnit.NotifyEntity(gc.getInfoTypeForString("MISSION_DAMAGE"))
		pUnit.setDamage(unitNewDamage, True)
		message1 = "Fort hits %s for " %(pUnit.getName()) 
		message2 = "%d damage." %(iDamage)
		messageTotal = message1 + message2
		self.alertIconPlayerFortAttacks(messageTotal, pUnit)

	

	def setPassFortDamage(self, pUnit):

		iX = pUnit.getX()
		iY = pUnit.getY()
		iPlayer = pUnit.getOwner()
		pPlayer = gc.getPlayer(iPlayer)
		eTeam = pPlayer.getTeam()

		point = pUnit.plot().getPoint()

		#effectType = gc.getInfoTypeForString('EFFECT_ARTILLERY_SHELL_EXPLODE')
		# To DO:
		# Make the sounds and effect Era specific, arrows for ancient, catapult for classical
		#CyEngine().triggerEffect(effectType, point)
		CyAudioGame().Play3DSound("AS3D_UN_GRENADE_EXPLODE",point.x,point.y,point.z)

		unitTempDamage = 0
		enemyFortCount = self.getEnemyFortCount(pUnit)

		strFortCount = str(enemyFortCount)

		if (enemyFortCount == 1):
			adjMessage = " Fort attacks %s." %(pUnit.getName())
		else:
			adjMessage = " Forts attack %s." %(pUnit.getName())

		startMessage = strFortCount + adjMessage
		
		self.alertActivePlayer(startMessage, 11)

		for iiX in range(iX-1, iX+2, 1):
			for iiY in range(iY-1, iY+2, 1):
				pPlot = CyMap().plot(iiX,iiY)
				if (pPlot.getImprovementType() == gc.getInfoTypeForString("IMPROVEMENT_FORT")):

					iDamage = self.getRandAttack(21, 10)

					if (iDamage == 0):
						missMessage = "Fort misses %s." %(pUnit.getName())
						self.alertIconPlayerFortAttacks(missMessage, pUnit, 10)
					else:
						unitTempDamage = unitTempDamage + iDamage

		bottomUnit = pPlot.getUnit(0)
		fortOwner = bottomUnit.getOwner()

		unitCurrentHP = pUnit.currHitPoints()
		newDamage = unitTempDamage

		if (self.isAttackFatal(pUnit, unitCurrentHP, newDamage)):
			self.killUnit(pUnit, fortOwner)
		else:
			self.damageUnit(pUnit, newDamage)


	def isAttackFatal(self, pUnit, iHitPoints, iDamage):
		if (iDamage >= iHitPoints):
			return True
		else:
			return False














